筆記目錄

Skip to content

在 ASP.NET Core MVC 使用 HTML5 實現多檔案上傳

TLDR

  • 使用 HTML5 的 <input type="file" multiple> 屬性即可支援多檔案選取。
  • 前端應使用 FormData 物件封裝檔案資料,並透過 Axios 的 onUploadProgress 監聽上傳進度。
  • 後端 Controller 接收參數時,應使用 IFormFileCollection 型別進行模型繫結。
  • 若需調整上傳檔案大小限制,可透過 FormOptions 進行全域設定,或使用 [RequestSizeLimit] 等 Attribute 進行個別 Action 的限制調整。
  • 務必注意 multipart/form-data 的 Content-Type 設定,使用 FormData 時瀏覽器會自動處理,不建議手動設定。

前端實作:FormData 與進度監聽

什麼情況下會遇到這個問題:當需要透過 AJAX 非同步上傳多個檔案,並即時顯示上傳進度條時。

在前端實作中,利用 FormData 物件可以方便地將多個檔案封裝為 Key/Value 格式。透過 Axios 的 onUploadProgress 事件,可以取得 loadedtotal 屬性,進而計算並更新進度條數值。

javascript
new Vue({
    el: '#app',
    data: {
        formData: new FormData,
        progressBarValue: 0
    },
    methods: {
        handleFileChange(e) {
            this.formData = new FormData();
            for (let i = 0; i < e.target.files.length; i++) {
                this.formData.append(e.target.id, e.target.files[i]);
            }
        },
        handleSubmit() {
            let config = {
                onUploadProgress: progressEvent => {
                    this.progressBarValue = (progressEvent.loaded / progressEvent.total * 100 | 0);
                }
            };

            axios.post('@Url.Action("Index3")', this.formData, config).then(response => {
                alert(response.data.message);
            }).catch(thrown => {
                alert(thrown);
            });
        }
    }
});

後端處理:IFormFileCollection 模型繫結

什麼情況下會遇到這個問題:當後端需要接收前端傳送的多個檔案,並希望直接透過 ViewModel 進行模型繫結時。

在 ASP.NET Core 中,處理多檔案上傳應使用 IFormFileCollection 型別。這能簡化過去在 MVC 5 中必須手動操作 Request.Files 的繁瑣過程。

csharp
public class IndexViewModel {
    [DisplayName("上傳檔案")]
    [Required]
    public IFormFileCollection Files { get; set; }
}

[HttpPost]
public async Task<IActionResult> Index3(IndexViewModel viewModel) {
    if (!ModelState.IsValid) {
        string message = ModelState.First(x => x.Value.Errors.Count > 0)
            .Value?.Errors.FirstOrDefault()?.ErrorMessage;
        return Ok(new { Message = message });
    }

    foreach (var formFile in viewModel.Files) {
        if (formFile.Length > 0) {
            var filePath = Path.GetTempFileName();
            using var stream = System.IO.File.Create(filePath);
            await formFile.CopyToAsync(stream);
        }
    }
    return Ok(new { Message = "上傳成功" });
}

調整檔案上傳大小限制

什麼情況下會遇到這個問題:當上傳的檔案超過預設限制(如預設的 128MB),導致伺服器拒絕請求時。

若要調整上傳限制,可選擇全域設定或針對特定 Action 設定。

全域設定

Program.cs 中調整 Kestrel 或 IIS 的請求大小限制:

csharp
builder.Services.Configure<FormOptions>(x => {
    x.MultipartBodyLengthLimit = long.MaxValue;
});

builder.WebHost.ConfigureKestrel(opt => {
    opt.Limits.MaxRequestBodySize = long.MaxValue;
});

builder.Services.Configure<IISServerOptions>(options => {
    options.MaxRequestBodySize = long.MaxValue;
});

Attribute 設定

針對特定 Action 進行限制調整,此設定優先級高於全域設定:

csharp
[HttpPost]
[DisableRequestSizeLimit]
[RequestFormLimits(MultipartBodyLengthLimit = long.MaxValue)]
public async Task<IActionResult> Index(IndexViewModel viewModel) {
    // ...
}

DANGER

注意事項

  • 設定 long.MaxValue 僅為範例,請依照實際需求設置限制大小。
  • 上述設定單位皆為 byte。
  • Attribute 優先度會高於全域設定。

異動歷程

  • 2022-10-24 初版文件建立。